package websocket

import (
	"encoding/json"
	"errors"
	"time"

	"github.com/gorilla/websocket"
)

//parse payloaded api message
func parseAPIMessage(msg []byte) (API, string, error) {
	check := struct {
		APIType string
	}{}
	err := json.Unmarshal(msg, &check)
	if err != nil {
		return nil, "", errors.New(("APIType parsing: " + err.Error()))
	}

	switch check.APIType {
	case apiTypes.SubscribeToChat:
		parsed := subscribeToChatAPI{}
		err := json.Unmarshal(msg, &parsed)
		if err != nil {
			return nil, check.APIType, errors.New((check.APIType + ": " + err.Error()))
		}

		var toAPI API
		toAPI = subscribeToChatAPI{}
		toAPI = parsed

		return toAPI, check.APIType, nil

	case apiTypes.SendMessage:
		parsed := sendMessageAPI{}
		err := json.Unmarshal(msg, &parsed)
		if err != nil {
			return nil, check.APIType, errors.New((check.APIType + ": " + err.Error()))
		}

		var toAPI API
		toAPI = subscribeToChatAPI{}
		toAPI = parsed

		return toAPI, check.APIType, nil

	case apiTypes.OnlineList:
		parsed := onlineListAPI{}
		err := json.Unmarshal(msg, &parsed)
		if err != nil {
			return nil, check.APIType, errors.New((check.APIType + ": " + err.Error()))
		}

		toAPI := parsed

		return toAPI, check.APIType, nil

	default:
		return nil, "", errors.New(("unknown APIType: " + check.APIType))
	}
}

func disconnect(c *websocket.Conn, listenChats []string) {
	for _, chatID := range listenChats {
		delete(Listeners[chatID], c)
		message := makeOnlineList(chatID)
		for cs := range Listeners[chatID] {
			message.exec(cs)
		}

		if len(Listeners[chatID]) == 0 {
			delete(Listeners, chatID)
		}
	}
}

var apiTypes = struct {
	SubscribeToChat string
	SendMessage     string
	OnlineList      string
}{
	SubscribeToChat: "subscribeToChat",
	SendMessage:     "sendMessage",
	OnlineList:      "onlineList",
}

//API interface for messages action executing
type API interface {
	exec(c *websocket.Conn, listenChats *[]string) error
}

//subscribe ws connection to chat
type subscribeToChatAPI struct {
	APIType  string `json:"APIType"`
	ChatID   string `json:"chatID"`
	UserName string `json:"userName"`
}

func (j subscribeToChatAPI) exec(c *websocket.Conn, listenChats *[]string) error {
	if _, exists := Listeners[j.ChatID]; !exists {
		Listeners[j.ChatID] = make(map[*websocket.Conn]string)
	}
	*listenChats = append(*listenChats, j.ChatID)
	Listeners[j.ChatID][c] = j.UserName
	message := newUserOnChatAPI{
		APIType:  apiOutTypes.NewUserOnChat,
		ChatID:   j.ChatID,
		UserName: j.UserName,
	}
	message2 := makeOnlineList(j.ChatID)
	for cs := range Listeners[j.ChatID] {
		message.exec(cs)
		message2.exec(cs)
	}
	return nil
}

type onlineListAPI struct {
	APIType string `json:"APIType"`
	ChatID  string `json:"chatID"`
}

func (j onlineListAPI) exec(c *websocket.Conn, listenChats *[]string) error {
	message := makeOnlineList(j.ChatID)
	message.exec(c)
	return nil
}

//forward message between clients
type sendMessageAPI struct {
	APIType   string `json:"APIType"`
	ChatID    string `json:"chatID"`
	UserName  string `json:"userName"`
	TimeStamp int64  `json:"timestamp"`
	Image     string `json:"image"`
	Message   string `json:"message"`
}

func (j sendMessageAPI) exec(c *websocket.Conn, listenChats *[]string) error {
	message := reciveMessageAPI{
		APIType:   apiOutTypes.ReciveMessage,
		ChatID:    j.ChatID,
		UserName:  j.UserName,
		TimeStamp: time.Now().Unix(),
		Image:     j.Image,
		Message:   j.Message,
	}
	for cs := range Listeners[j.ChatID] {
		err := message.exec(cs)
		if err != nil {
			return errors.New(("sendMessageAPI: " + err.Error()))
		}
	}
	return nil
}

var apiOutTypes = struct {
	NewUserOnChat    string
	ReciveMessage    string
	ReciveOnlineList string
}{
	NewUserOnChat:    "newUserOnChat",
	ReciveMessage:    "reciveMessage",
	ReciveOnlineList: "ReciveOnlineList",
}

//APIOut interface for ???)
type APIOut interface {
	exec(c *websocket.Conn) error
}

//report to ws connection on new user on chat
type newUserOnChatAPI struct {
	APIType  string `json:"APIType"`
	ChatID   string `json:"chatID"`
	UserName string `json:"userName"`
}

func (j newUserOnChatAPI) exec(c *websocket.Conn) error {
	jMarshaled, err := json.Marshal(j)
	if err != nil {
		return errors.New(("newUserOnChatAPI: " + err.Error()))
	}
	c.WriteMessage(1, jMarshaled)
	return nil
}

//forward message between clients
type reciveMessageAPI struct {
	APIType   string `json:"APIType"`
	ChatID    string `json:"chatID"`
	UserName  string `json:"userName"`
	TimeStamp int64  `json:"timestamp"`
	Image     string `json:"image"`
	Message   string `json:"message"`
}

func (j reciveMessageAPI) exec(c *websocket.Conn) error {
	jMarshaled, err := json.Marshal(j)
	if err != nil {
		return errors.New(("reciveMessageAPI: " + err.Error()))
	}
	c.WriteMessage(1, jMarshaled)
	return nil
}

type reciveOnlineListAPI struct {
	APIType    string   `json:"APIType"`
	ChatID     string   `json:"chatID"`
	OnlineList []string `json:"onlineList"`
}

func (j reciveOnlineListAPI) exec(c *websocket.Conn) error {
	jMarshaled, err := json.Marshal(j)
	if err != nil {
		return errors.New(("reciveOnlineListAPI: " + err.Error()))
	}
	c.WriteMessage(1, jMarshaled)
	return nil
}

func makeOnlineList(chatID string) reciveOnlineListAPI {
	onlineList := make([]string, 0)
	for cs := range Listeners[chatID] {
		onlineList = append(onlineList, Listeners[chatID][cs])
	}
	message := reciveOnlineListAPI{
		APIType:    apiOutTypes.ReciveOnlineList,
		ChatID:     chatID,
		OnlineList: onlineList,
	}
	return message
}
